package auctioncentral;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;

/**
 * This class provides functionality for the other classes to interact with the database. 
 * 
 * @author Kyle Scharnhorst, Andres Rosales (acr)
 * @version 0.3 acr 
 */
public class AuctionSystem {
  
  //instance fields
  /**
   * The size of the array for holding events.
   */
  private static final int EVENT_LIST_SIZE = 5000;
  
  /**
   * The user who logged in. 
   */
  private AbstractUser my_current_user; 
  
  /**
   * The List of all the events.
   */
  private List<Event> my_events = new ArrayList<Event>();
  
  /**
   * List of users
   */
  private List<AbstractUser> my_users = new ArrayList<AbstractUser>();
  
  /**
   *  list of only auction central users.
   */
  private List<AuCenStaffUser> my_auction_central_users = new ArrayList<AuCenStaffUser>();
  
  /**
   *  list of only non profit users.
   */
  private List<AuthNonProfitUser> my_non_profit_users = new ArrayList<AuthNonProfitUser>();
  
  /**
   *  list of only bidder users.
   */
  private List<BiddingUser> my_bidder_users = new ArrayList<BiddingUser>();
  
  /**
   * list of all items.
   */
  private List<Item> my_items = new ArrayList<Item>();
  
  /**
   * list of all bids.
   */
  private List<Bid> my_bids = new ArrayList<Bid>();
  
  /**
   * The database object that will allow functionality to retrieve and store data. 
   */
  private Database my_database = new Database();
  
  //constructor(s)
  /**
   * Constructor for this class sets gets all the data in the data base
   * and initializes the lists to used for data manipulation.
   */
  public AuctionSystem() {
    my_auction_central_users = my_database.retrieveAuctionCentralStaffUsers();
    my_non_profit_users = my_database.retrieveNonProfitUsers();
    my_bidder_users = my_database.retrieveBidderUsers();
    buildAllUserList();
    my_events = retrieveEvents();
    my_items = my_database.retrieveItems();
    my_bids = my_database.retrieveBids();
    
    //my_users.add(new AuthNonProfitUser("dre", "", "", ""));
    getEventItems();
    //******print statements for testing for reading in. Don't remove.*******
    
//    for (Event e: my_events) {
//        System.out.println(e.toString());
//      }
//    
//    for (AbstractUser e: my_users) {
//      System.out.println(e.toString());
//    }
//    
//    for (Item e: my_items) {
//      System.out.println(e.toString());
//    }
//    
//    for (Bid e: my_bids) {
//      System.out.println(e.toString());
//    }
    //************************
  }
  
  /**
   * This method builds a list that contains all users.
   *  <dt><b>Precondition:</b><dd> User lists have already been read in from the database.
   *  <dt><b>Postcondition:</b><dd> This class now contains a list of all users.
   */
  private void buildAllUserList() {
    for (AuCenStaffUser e : my_auction_central_users) {
      my_users.add(e);
    }
    
    for (AuthNonProfitUser e : my_non_profit_users) {
      my_users.add(e);
    }
    
    for (BiddingUser e : my_bidder_users) {
      my_users.add(e);
    }
  }

  //methods
  
  /**
   * Returns a list of bids associated with the given bidder name.
   *  <dt><b>Precondition:</b><dd> Data has been read in from the database.
   *  <dt><b>Postcondition:</b><dd> A list of bid objects has been returned.
   * @param some_user the user string to search for bids containing that string.
   * @return List of bids for that bidder name. Returns null if no bids found.
   */
  public List<Bid> findBidsFromUser(String some_user) {
	  List<Bid> allDemBids = new ArrayList<Bid>();
	  
	  boolean noBidsFound = true;
	  
	  for (Bid b: my_bids) {
		  if (b.getBidder().equals(some_user)) {
			  allDemBids.add(b);
			  noBidsFound = false;
		  }
	  }
	  
	  if (noBidsFound) {
		  allDemBids = null;
	  }
	  
	  return allDemBids;
  }
  
  /**
   * Returns the Event that the given bid is on.
   *  <dt><b>Precondition:</b><dd> Data has been read in from the database.
   *  <dt><b>Postcondition:</b><dd> A String of the Event name has been returned.
   * @param some_bid the bid to find what event it belongs to.
   * @return the name of the event the bid is associated with (contains the item the bid was made on).
   */
  public String findEventFromBid(final Bid some_bid) {
	  return findItemByID(some_bid.getItemId()).getMy_event();
  }
  
	/**
	 * Returns the item if the given ID matches an item in the master inventory. Returns null otherwise.
	 * 
	 * @param some_id the Item id of the item you wish to find.
	 * @return the Item with the given ID, null if none match the given ID.
	 */
	public Item findItemByID(int some_id) {
		Item theResult = null;
		
		for (Item i: my_items) {
			if (i.getMy_id() == some_id){
				theResult = i;
				break;
			}
		}
		return theResult;
	}
  
  
  /**
   * This method searches through a list of events to find a particular event.
   * @author acr
   *  <dt><b>Precondition:</b><dd> The lists have been read in from the database.
   *  <dt><b>Postcondition:</b><dd> The result if the event was found has been returned.
   * @param some_event an event to find.
   * @return True if the event exists in the list and false otherwise.
   */
  public boolean findEvent(final Event some_event) {
	  return my_events.contains(some_event);
  }
  
  /**
   * helper for creating events.
   *  <dt><b>Precondition:</b><dd> The lists have been read in from the database.
   *  <dt><b>Postcondition:</b><dd> event item list has been populated.
   */
  public void getEventItems() {
    
    for(Event e: my_events){    
      for(Item i:my_items) {
        if(e.getName().equals(i.getMy_event())) {
          e.addItems(i);
        }
      }
    }

  }
  
  /**
   * This method gets all of the events from the database.
   *  <dt><b>Precondition:</b><dd> The connection to the database has been established.
   *  <dt><b>Postcondition:</b><dd> A list of Event objects has been built and returned.
   * @return the list of Event objects.
   */
  public List<Event> retrieveEvents() {
    List<EventDatabaseHelper> helpers = my_database.retrieveEventDatabaseHelpers();
    List<Event> events = new ArrayList<>();
    
    for (EventDatabaseHelper a: helpers) {
      Calendar date = new GregorianCalendar(a.getMy_year(), a.getMy_month(), a.getMy_day(), a.getMy_hour(), a.getMy_minute());
      AbstractUser non_profit_user = getUser(a.getMy_user());
      //AbstractUser auction_central_user = getUser();
       events.add(new Event(a.getMy_name(), non_profit_user.getName(), a.getMy_contact_num(), a.getMy_intake_user(), date, a.getMy_duration(), a.getMy_est_items(), a.getMy_comments()));
      }
    
    return events;
  }
  
  /**
   * Tries to add the given event to the list of events. Returns true if successful,
   * false if otherwise.
   * @author acr
   * <dt><b>Precondition:</b><dd> An Event has been made without null fields.
   * That is it has been filled out completely.
   * <dt><b>Postcondition:</b><dd> An event will be added if the result of this method is true
   * and an error will be displayed otherwise.
   * @param the_event The event to attempt adding.
   * @return True if the given event is successfully added to the list of events, false
   * if otherwise.
   */
  public boolean addEvent(final Event the_event) {
	  boolean result = false;
	  Calendar currentDate = Calendar.getInstance();
	  Calendar newDate = the_event.getDate();
	  
	  if (the_event != null) {
		  // Checks to see if the new event satisfies all business rules for scheduling
		  // new events.
		  if (tallyOngoingEvents() < 25 && currentDate.compareTo(newDate) < 0
			  && Math.abs(newDate.get(Calendar.MONTH) - currentDate.get(Calendar.MONTH)) <= 3 
			  && checkDayBusinessRules(the_event) && checkWeekEvents(the_event) 
			  && !alreadyHadAnEvent(the_event)) {
			  my_events.add(the_event);
			  my_database.updateEventTable(my_events);
			  result = true;
		  }
	  }
	  return result;
  }
  
  public boolean addBid(final Bid the_bid) {
	  boolean result = false;
	  
	  result = my_bids.add(the_bid);
	  
	  return result;
  }
  
  public int addItem(final Item the_item) {
//	  my_items.add(the_item);
//	  the_event.addItems(the_item);
	  int current_id = the_item.getMy_id();
		int current_check = 0;
		
		boolean flag = false;
		if(!my_items.isEmpty())
		{
			for(int x = 0; x < my_items.size(); x++)//Item i : my_items)
			{
				if (current_id < my_items.get(x).getMy_id())
				{
					flag = true;
					my_items.add(my_items.indexOf(my_items.get(x)), the_item);
					break;
				}
			}
			if(!flag)
			{
				my_items.add(the_item);
			}
			 
		}
		else
		{
	       my_items.add(the_item);
		}
	  return the_item.getMy_id();
  }
  
  
  /**
   * Removes the given event from the current list of events.
   * @author acr
   *  <dt><b>Precondition:</b><dd> An Event in this auction system's list has been passed in.
   *  <dt><b>Postcondition:</b><dd> The Event will be removed.
   * @param the_event The event to be removed.
   */
  public void removeEvent(Event the_event) {
	  my_events.remove(the_event);
  }

/**
   * Checks if the database contains the given user.
   * @author acr
   *  <dt><b>Precondition:</b><dd> Database data has been read into the system.
   *  <dt><b>Postcondition:</b><dd> The user will be returned or null if there is no user
   *  that matches the given string.
   * @param the_user The user to check for.
   * @return The user in the database whose user name is the given String, if it is
   * contained within the database. Returns null if it isn't.
   */
  public AbstractUser getUser(final String the_user) {
  	AbstractUser result = null;
    for (AbstractUser a: my_users) {
  	  if(a.my_name.equals(the_user)){
  	    result = a;
  	    break;
  	  }
  	}
    return result;
  }
  
  /**
   * Checks if the database contains the given user.
   * @author acr
   *  <dt><b>Precondition:</b><dd> The database data has been read in.
   *  <dt><b>Postcondition:</b><dd> The result of if the user exists in the database
   *  has been returned.
   * @param the_user The user to check for.
   * @return The user in the database whose username is the given String, if it is
   * contained within the database. Returns null if it isn't.
   */
  public boolean hasUser(final String the_user) {
    boolean result = false;
    for (AbstractUser a: my_users) {
      if(a.my_name.equals(the_user)){
        result = true;
        break;
      }
    }
    return result;
  }
  
  //getters and setters
  /**
   * Sets my event list.
   * @author Kyle Scharnhorst
   * @param some_event_list some list of events to set my_events to.
   */
  public void setEventsList(final List<Event> some_event_list) {
    my_events = some_event_list;
    
    //my_database.updateEventTable(my_events);
    //saveData();
  }
  
  /**
   * stores all data into the database.
   *  <dt><b>Precondition:</b><dd> Database data has been read in.
   *  <dt><b>Postcondition:</b><dd> All the objects in the event, item, and bid list 
   *  has now been stored into the database.
   */
  public void saveData() {
    my_database.updateEventTable(my_events);
    my_database.updateItemTable(my_items);
    my_database.updateBidTable(my_bids);
   // my_database.updateNonProfit(my_non_profit_users);
  }
  
  /**
   * Gets the event list in this AuctionSystem.
   */
  public List<Event> getEventList() {
    return my_events;
  }
  
  /**
   * Sets the users list.
   * @param some_list_of_users
   */
  public void setUserList(final List<AbstractUser> some_list_of_users) {
    my_users = some_list_of_users; 
  }
  
  /**
   * Tallies up the number of ongoing events in the list of events.
   * @author acr
   *  <dt><b>Precondition:</b><dd> data has been read in.
   *  <dt><b>Postcondition:</b><dd> The count of ongoing events has been returned.
   * @return The number of ongoing events in the list of events.
   */
  protected int tallyOngoingEvents() {
	  Calendar current = Calendar.getInstance();
	  int count = 0;
	  
	  for (Event the_e: my_events) {
		  if (current.compareTo(the_e.getDate()) < 0) {
			  count++;
		  }
	  }
	  return count;
  }
  
  /**
   * Checks to see if the organization requesting a given event has already had an event
   * this year. Returns false if they haven't, true if they have.
   * @author acr
   *  <dt><b>Precondition:</b><dd> The event has been named after the organization.
   *  <dt><b>Postcondition:</b><dd> The result if the organization already had an event this year
   *  has been returned.
   * @param the_event The event who's owner is being investigated.
   * @return False if the owner of the given event hasn't had another event this year,
   * true if otherwise.
   */
  
  //(the_e.getName().substring(0, the_e.getName().indexOf('-')).equals(the_event.getName().substring(0, the_event.getName().indexOf('-')))
  //&& the_event.getDate().get(Calendar.YEAR) == the_event.getDate().get(Calendar.YEAR)
  private boolean alreadyHadAnEvent(final Event the_event) {
	  for (Event the_e: my_events) {		  
		  if (the_event.getUser().equals(the_e.getUser())) { //  
			  return true;
		  }
	  }
	  return false;
  }
  
  /**
   * Checks to see if the given event satisfies the daily business rules for scheduling 
   * and returns true if it does, and returns false if it doesn't.
   * @author acr
   *  <dt><b>Precondition:</b><dd> Event has been inputed by user.
   *  <dt><b>Postcondition:</b><dd> The result of if the event fits within the 
   *  parameters of the business rules has been returned.
   * @param new_event The event to be checked.
   * @return True if the given event satisfies the daily business rules, false if it doesn't.
   */
  private boolean checkDayBusinessRules(Event new_event) {
	  if (new_event != null) {
		  Calendar newDate = new_event.getDate();
		  Event firstEvent = null;
		  Event secondEvent = null;
		
		  for (Event the_e: my_events) {
			  if (newDate.get(Calendar.DAY_OF_YEAR) == the_e.getDate().get(Calendar.DAY_OF_YEAR)) {
				  if (newDate.compareTo(the_e.getDate()) > 0) {
					  firstEvent = the_e;
					  secondEvent = new_event;
				  } else {
					  firstEvent = new_event;
					  secondEvent = the_e;
				  }
				  
				  if (secondEvent.getDate().get(Calendar.HOUR_OF_DAY) 
					  - (firstEvent.getDate().get(Calendar.HOUR_OF_DAY) 
					  + firstEvent.getDuration()) < 2) {
					  return false;
				  }
			  }
			  
			  int countWeek = 0;
			  int countDay = 0;
			  if (newDate.get(Calendar.DAY_OF_YEAR) == the_e.getDate().get(Calendar.DAY_OF_YEAR)) {
				  countDay++;
				  
				  // No more than two events may be scheduled on the same day.
				  if (countDay > 2) {
					  return false;
				  }
			  }
		  } 
		  return true;
	  }
	  return false;
  }
  
  /**
   * The business rule that checks for to see if there are more than
   *  5 events in a 7 day period.
   *  <dt><b>Precondition:</b><dd> Event has been inputed by user.
   *  <dt><b>Postcondition:</b><dd> The result of if the event fits within the 
   *  parameters of the business rules has been returned.
   * @param the_event
   * @return true if the business rule passes, and false otherwise.
   */
  private boolean checkWeekEvents(Event the_event) {
	  int eventCount1;
	  int eventCount2;
	  int dateDifference;
	  for (int i = 0; i < 7; i++) {
		  eventCount1 = 1;
		  eventCount2 = 1;
		  for (Event e: my_events) {
			  dateDifference = the_event.getDate().get(Calendar.DAY_OF_YEAR) - e.getDate().get(Calendar.DAY_OF_YEAR);

			  if (dateDifference >= 0 - i && dateDifference <= 6 - i) {
				  eventCount1++;
			  }
			  			  
			  if (dateDifference >= 0 - (6 - i) && dateDifference <= i) {
				  eventCount2++;
			  }
			  if (eventCount1 > 5 || eventCount2 > 5) {
				  return false;
			  }
		  }
	  }
	  return true;
  }


public List<AuCenStaffUser> getMy_auction_central_users() {
  return my_auction_central_users;
}

public void setMy_auction_central_users(
    List<AuCenStaffUser> my_auction_central_users) {
  this.my_auction_central_users = my_auction_central_users;
}

public List<AuthNonProfitUser> getMy_non_profit_users() {
  return my_non_profit_users;
}

public void setMy_non_profit_users(List<AuthNonProfitUser> my_non_profit_users) {
  this.my_non_profit_users = my_non_profit_users;
}

public List<BiddingUser> getMy_bidder_users() {
  return my_bidder_users;
}

public void setMy_bidder_users(List<BiddingUser> my_bidder_users) {
  this.my_bidder_users = my_bidder_users;
}

public List<Item> getMy_items() {
  return my_items;
}

public void setMy_items(List<Item> my_items) {
  this.my_items = my_items;
}

public List<Bid> getMy_bids() {
  return my_bids;
}

public void setMy_bids(List<Bid> my_bids) {
  this.my_bids = my_bids;
}

public AbstractUser getMy_current_user() {
  return my_current_user;
}

public void setMy_current_user(AbstractUser my_current_user) {
  this.my_current_user = my_current_user;
}

/**
 * Finds an event with the given name.
 * @author acr
 *  <dt><b>Precondition:</b><dd> Data has been read in from the database.
 *  <dt><b>Postcondition:</b><dd> The result of if the event fits within the 
 * @param the_name The name to use to search for an event.
 * @return The event with the given name if it exists. Returns null if it doesn't exist.
 */
public Event findEventFromName(String the_name) {
	for (Event e: my_events) {
		if (e.getName().equals(the_name)) {
			return e;
		}
	}
	return null;
}
  

  
}